package service

import (
	"bytes"
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	//kelleyRabbimqPool "gitee.com/tym_hmm/rabbitmq-pool-go"
	"github.com/bitly/go-simplejson"
	"github.com/pyroscope-io/pyroscope/pkg/agent/profiler"
	"github.com/streadway/amqp"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	OrgRate "golang.org/x/time/rate"
	"io/ioutil"
	"net/http"
	"reflect"
	"regexp"
	"strings"
	"time"
	"webhook-alarm/rabbitmqpool"
)

func SetGroupMap(chat *simplejson.Json) {
	GroupMap = make(map[string]interface{})
	for _, v := range chat.Get("webhook_list").MustArray() {
		for group, serverList := range v.(map[string]interface{}) {
			GroupMap[group] = serverList
		}
	}
}

func GetAccessToken(handler *Handler, token map[string]string) {
	type JSON struct {
		AccessToken string `json:"access_token"`
	}
	var corpId string
	var corpSecret string
	var qyurl string
	var body []byte
	var bodyErr, getErr, jsonErr error
	var req *http.Response
	client := &http.Client{}

	for {
		for _, v := range handler.wechat.Get("webhook_list").MustArray() {
			for group, serviceList := range v.(map[string]interface{}) {
				GroupMap[group] = serviceList
				ZapLog.Info("webhook-alarm",
					zap.Any("group:", group),
					zap.Any("serviceList", serviceList),
				)
				corpId = MyHandler.wechat.Get(group).Get("corp_id").MustString()
				corpSecret = MyHandler.wechat.Get(group).Get("corp_secret").MustString()
				qyurl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", corpId, corpSecret)
				req, getErr = client.Get(qyurl)
				if getErr != nil {
					panic(getErr)
				}
				body, bodyErr = ioutil.ReadAll(req.Body)
				if bodyErr != nil {
					panic(bodyErr)
				} else {
					req.Body.Close()
				}
				ZapLog.Info("webhook-alarm",
					zap.Any("AlarmGroup:", group),
					zap.Any("WechatToken", string(body)),
				)
				var jsonStr JSON
				jsonErr = json.Unmarshal(body, &jsonStr)
				if jsonErr != nil {
					panic(jsonErr)
				}
				token[group] = jsonStr.AccessToken
			}
		}

		time.Sleep(1 * time.Hour)
	}
}

func SearchSrevice(serviceName string, groupmap map[string]interface{}) []string {
	var regexpBool bool
	var groupSlice []string

	for group, serviceList := range groupmap {
		s := reflect.ValueOf(serviceList)
		for serviceListNum := 0; serviceListNum < s.Len(); serviceListNum++ {
			serviceNameRegexp := s.Index(serviceListNum).Elem().String()
			regexpBool, _ = regexp.MatchString(serviceNameRegexp, serviceName)
			if regexpBool {
				groupSlice = append(groupSlice, group)
				break
			}
		}

	}
	return groupSlice
}

func (my *Handler) ServeHTTP(body []byte, sentPurpose string, timing string) {
	res, err := simplejson.NewJson(body)
	if err != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("TraceId", timing),
			zap.Any("err", err),
			zap.Any("Body", string(body)),
		)
		return
	} else {
		ZapLog.Info("webhook-alarm",
			zap.Any("TraceId", timing),
			zap.Any("AlarmBody", string(body)),
		)
	}

	go PushMsgToMongoDb(MongodbClient, body, "alarm", timing)

	serviceName := ""
	switch sentPurpose {
	case "sentry":
		alarmType := "Sentry"
		issueId := res.Get("id").MustString()
		projectName := res.Get("project").MustString()
		message := res.Get("message").MustString()
		culprit := res.Get("event").Get("culprit").MustString()
		url := res.Get("url").MustString()
		tagsLen := len(res.Get("event").Get("tags").MustArray())

		for tagNum := 0; tagNum < tagsLen; tagNum++ {
			if strings.Compare(res.Get("event").Get("tags").GetIndex(tagNum).GetIndex(0).MustString(), "server_name") == 0 {
				serviceName = res.Get("event").Get("tags").GetIndex(tagNum).GetIndex(1).MustString()
				break
			}
		}

		msg := fmt.Sprintf("Alarm_type: %s\nProject:  %s\nIssue_service:  %s\nIssue_id:  %s\nIssue_message:  %s\nIssue_culprit:  %s\nIssue_url:  %s", alarmType, projectName, serviceName, issueId, message, culprit, url)
		go (SetMongoDbSentryMsgFormat(projectName, serviceName, issueId, message, culprit, url, timing)).PushSentryMsgToMongoDb(MongodbClient, sentPurpose)
		go PushlishPool(msg, sentPurpose, serviceName, timing)
	case "skywalking":
		for bodyNum := range res.MustArray() {
			alarmType := "Skywalking"
			scope := res.GetIndex(bodyNum).Get("scope").MustString()
			name := res.GetIndex(bodyNum).Get("name").MustString()
			ruleName := res.GetIndex(bodyNum).Get("ruleName").MustString()
			alarmMessage := res.GetIndex(bodyNum).Get("alarmMessage").MustString()
			tagsLen := len(res.GetIndex(bodyNum).Get("tags").MustArray())
			for tagNum := 0; tagNum < tagsLen; tagNum++ {
				if strings.Compare(res.GetIndex(bodyNum).Get("tags").GetIndex(tagNum).Get("key").MustString(), "server_name") == 0 {
					serviceName = res.GetIndex(bodyNum).Get("tags").GetIndex(tagNum).Get("value").MustString()
					break
				}
			}

			msg := fmt.Sprintf("Alarm_type: %s\nScope:  %s\nName:  %s\nRuleName:  %s\nAlarmMessage:  %s", alarmType, scope, name, ruleName, alarmMessage)
			go (SetMongoDbSkyMsgFormat(scope, name, ruleName, alarmMessage, timing)).PushSkyMsgToMongoDb(MongodbClient, sentPurpose)
			go PushlishPool(msg, sentPurpose, serviceName, timing)
		}
	}
}

func SendMessage(agentid, totag, msg string, token string, groupName string, timing string) bool {
	req := map[string]interface{}{
		"agentid": agentid,
		"msgtype": "text",
		"totag":   totag,
		"text": map[string]interface{}{
			"content": msg,
		},
		"safe": 0,
	}
	url := []byte("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=")
	url = append(url, []byte(token)...)
	return HttpPostjson(string(url), req, groupName, timing)
}

func HttpPostjson(url string, data map[string]interface{}, groupName string, timing string) bool {
	jsonData, err := json.Marshal(data)
	if err != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("TraceId", timing),
			zap.Any("GroupName", groupName),
			zap.Any("JsonMarshalError", err),
		)
		return false
	} else {
		ZapLog.Info("webhook-alarm",
			zap.Any("TraceId", timing),
			zap.Any("GroupName", groupName),
			zap.Any("WechatBody", string(jsonData)),
		)
	}
	resp, err := http.Post(url, "application/json", bytes.NewReader(jsonData))
	if err != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("TraceId", timing),
			zap.Any("GroupName", groupName),
			zap.Any("PostError", err),
		)
		return false
	}
	body, err := ioutil.ReadAll(resp.Body)
	ZapLog.Info("webhook-alarm",
		zap.Any("TraceId", timing),
		zap.Any("GroupName", groupName),
		zap.Any("WechatResponse", string(body)),
	)
	defer resp.Body.Close()
	return true
}

func Parse() {
	var scope = flag.Bool("scope", false, "性能监测开关")
	var scopeServerAddr, logType, rabbitmqAddr, rabbitmqUser, rabbitmqPass, rabbitmqVhost, mongoAddr string
	rateApi := flag.Int("rate", 100, "指定每秒发送频率")
	mqPort := flag.Int("mqport", 5672, "指定mq端口")
	flag.StringVar(&scopeServerAddr, "s", "localhost:4040", "指定pyroscope服务端地址")
	flag.StringVar(&rabbitmqAddr, "mqaddr", "localhost", "指定rabbitmq地址")
	flag.StringVar(&rabbitmqUser, "mquser", "admin", "指定rabbitmq地址")
	flag.StringVar(&rabbitmqPass, "mqpass", "admin", "指定rabbitmq地址")
	flag.StringVar(&rabbitmqVhost, "mqvhost", "/alarm", "指定mongodb地址")
	flag.StringVar(&mongoAddr, "mgaddr", "mongodb://localhost:27017", "指定vhost")
	flag.StringVar(&logType, "logType", "", "指定日志类型，container: 控制台日志，否则日志输出到控制台及文件")
	flag.Parse()
	limiteRate = OrgRate.Every(time.Duration(1000 / *rateApi) * time.Millisecond)
	limit = OrgRate.NewLimiter(limiteRate, *rateApi)
	encoderConfig := zapcore.EncoderConfig{
		TimeKey:       "time",
		LevelKey:      "level",
		NameKey:       "logger",
		CallerKey:     "caller",
		MessageKey:    "serviceName",
		StacktraceKey: "stacktrace",
		LineEnding:    zapcore.DefaultLineEnding,
		EncodeLevel:   zapcore.CapitalLevelEncoder,
		EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
			enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
		},
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.FullCallerEncoder,
	}

	atom := zap.NewAtomicLevelAt(zap.InfoLevel)
	config := zap.Config{}
	if logType == "Container" {
		config = zap.Config{
			Level:            atom,
			Development:      false,
			DisableCaller:    true,
			Encoding:         "console",
			EncoderConfig:    encoderConfig,
			OutputPaths:      []string{"stdout"},
			ErrorOutputPaths: []string{"stderr"},
		}
	} else {
		config = zap.Config{
			Level:            atom,
			Development:      false,
			DisableCaller:    true,
			Encoding:         "console",
			EncoderConfig:    encoderConfig,
			OutputPaths:      []string{"stdout", "./log/alarm.log"},
			ErrorOutputPaths: []string{"stderr", "./log/alarm.log"},
		}
	}
	var err error
	ZapLog, err = config.Build()
	if err != nil {
		panic(fmt.Sprintf("ZapLog init failed: %v", err))
	} else {
		ZapLog.Info("webhook-alarm",
			zap.Any("ZapLogStatus", "success"),
		)
	}

	if *scope {
		scopeServerAddr := fmt.Sprintf("http://%s", scopeServerAddr)
		_, err := profiler.Start(profiler.Config{
			ApplicationName: "webhook-alarm",
			ServerAddress:   scopeServerAddr,
		})
		if err != nil {
			ZapLog.Error("webhook-alarm",
				zap.Any("ScopeStartError", err),
			)
		} else {
			ZapLog.Info("webhook-alarm",
				zap.Any("ScopeStartStatus", "success"),
			)
		}
	}

	token = make(map[string]string)
	wechatConf, _ := ioutil.ReadFile("./wechat.conf")
	ZapLog.Info("webhook-alarm",
		zap.Any("WechatConf", string(wechatConf)),
	)
	var parseErr error
	MyHandler.wechat, parseErr = simplejson.NewJson(wechatConf)
	if parseErr != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("WechatConfParseErr", parseErr),
		)
		panic(parseErr)
	}
	//Set_GroupMap(MyHandler.wechat)
	GroupMap = make(map[string]interface{})
	go GetAccessToken(MyHandler, token)
	CreateRabbitmqQueue(rabbitmqAddr, *mqPort, rabbitmqUser, rabbitmqPass, rabbitmqVhost)
	instanceRPool = initrabbitmq(rabbitmqAddr, *mqPort, rabbitmqUser, rabbitmqPass, rabbitmqVhost)
	instanceConsumePool = initConsumerabbitmq(rabbitmqAddr, *mqPort, rabbitmqUser, rabbitmqPass, rabbitmqVhost)
	go Consume("sentry")
	go Consume("skywalking")
	MongodbClient, err = ConnMongodb(mongoAddr, 10, 100)
	if err != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("ConnMongoDbErr", err),
		)
		panic(err)
	}
	time.Sleep(3 * time.Second)
	fmt.Println(welcome)
}

func initrabbitmq(mqaddr string, mqport int, mquser string, mqpass string, mqvhost string) *kelleyRabbimqPool.RabbitPool {
	oncePool.Do(func() {
		instanceRPool = kelleyRabbimqPool.NewProductPool()
		err := instanceRPool.Connect(mqaddr, mqport, mquser, mqpass, mqvhost)
		if err != nil {
			fmt.Println(err)
		}
	})
	return instanceRPool
}

func PushlishPool(msg string, alarmType string, serviceName string, timing string) {
	data := kelleyRabbimqPool.GetRabbitMqDataFormat("alarm", kelleyRabbimqPool.EXCHANGE_TYPE_DIRECT, alarmType, alarmType, msg, serviceName, timing)
	err := instanceRPool.Push(data)
	if err != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("TraceId", timing),
			zap.Any("ServiceName", serviceName),
			zap.Any("MsgPushError", err),
		)
	}
}

func initConsumerabbitmq(mqaddr string, mqport int, mquser string, mqpass string, mqvhost string) *kelleyRabbimqPool.RabbitPool {
	onceConsumePool.Do(func() {
		instanceConsumePool = kelleyRabbimqPool.NewConsumePool()
		//instanceConsumePool.SetMaxConsumeChannel(100)
		err := instanceConsumePool.Connect(mqaddr, mqport, mquser, mqpass, mqvhost)
		if err != nil {
			fmt.Println(err)
		}
	})
	return instanceConsumePool
}

func Consume(queueName string) {
	nomrl := &kelleyRabbimqPool.ConsumeReceive{
		ExchangeName: "alarm", //交换机名称
		ExchangeType: kelleyRabbimqPool.EXCHANGE_TYPE_DIRECT,
		Route:        "",
		QueueName:    queueName,
		IsTry:        true,  //是否重试
		IsAutoAck:    false, //自动消息确认
		MaxReTry:     5,     //最大重试次数
		EventFail: func(code int, e error, data []byte) {
			fmt.Printf("error:%s", e)
		},
		EventSuccess: func(data []byte, header map[string]interface{}, retryClient kelleyRabbimqPool.RetryClientInterface, appId string, msgId string, queueName string) bool { //如果返回true 则无需重试
			if SentMsg(data, appId, msgId, queueName) {
				_ = retryClient.Ack()
				return true
			} else {
				return false
			}
		},
	}
	instanceConsumePool.RegisterConsumeReceive(nomrl)
	err := instanceConsumePool.RunConsume()
	if err != nil {
		fmt.Println(err)
	}
}

func CreateRabbitmqQueue(mqaddr string, mqport int, mquser string, mqpass string, mqvhost string) {
	mqAddr := fmt.Sprintf("amqp://%s:%s@%s:%d%s", mquser, mqpass, mqaddr, mqport, mqvhost)
	conn, err := amqp.Dial(mqAddr)
	failOnError(err, "Failed to connect to RabbitMQ")
	defer conn.Close()
	ch, err := conn.Channel()
	failOnError(err, "Failed to open a channel")
	defer ch.Close()
	err = ch.ExchangeDeclare("alarm", "direct", true, false, false, false, nil)
	failOnError(err, "Failed to declare an exchange")

	_, err = ch.QueueDeclare("skywalking", true, false, false, false, nil)
	failOnError(err, "Failed to declare an queue")
	err = ch.QueueBind("skywalking", "skywalking", "alarm", false, nil)
	failOnError(err, "Bind queue to exchange failure")

	_, err = ch.QueueDeclare("sentry", true, false, false, false, nil)
	failOnError(err, "Failed to declare an queue")
	err = ch.QueueBind("sentry", "sentry", "alarm", false, nil)
	failOnError(err, "Bind queue to exchange failure")
}

func SentMsg(msg []byte, appId string, msgId string, queueName string) bool {
	switch queueName {
	case "sentry":
		limit.Wait(contextValue)
		groupSlice := SearchSrevice(appId, GroupMap)
		if len(groupSlice) == 0 {
			ZapLog.Warn("webhook-alarm",
				zap.Any("TraceId", msgId),
				zap.Any("ServiceName", appId),
				zap.Any("MatchGroupError", "does not belong to any group"),
			)
		} else {
			for _, groupNameSub := range groupSlice {

				totag := MyHandler.wechat.Get(groupNameSub).Get("to_tag").MustString()
				agentid := MyHandler.wechat.Get(groupNameSub).Get("agent_id").MustString()
				tokens := token[groupNameSub]
				return SendMessage(agentid, totag, string(msg), tokens, groupNameSub, msgId)
			}
		}

	case "skywalking":
		limit.Wait(contextValue)
		groupSlice := SearchSrevice(appId, GroupMap)

		if len(groupSlice) == 0 {
			ZapLog.Warn("webhook-alarm",
				zap.Any("TraceId", msgId),
				zap.Any("ServiceName", appId),
				zap.Any("MatchGroupError", "does not belong to any group"),
			)
		} else {
			for _, groupNameSub := range groupSlice {
				totag := MyHandler.wechat.Get(groupNameSub).Get("to_tag").MustString()
				agentid := MyHandler.wechat.Get(groupNameSub).Get("agent_id").MustString()
				tokens := token[groupNameSub]
				return SendMessage(agentid, totag, string(msg), tokens, groupNameSub, msgId)
			}
		}
	default:
		fmt.Println("我也不知道干啥，就想有default")
	}
	return true
}

func failOnError(err error, msg string) {
	if err != nil {
		ZapLog.Warn("webhook-alarm",
			zap.Any("Msg", msg),
			zap.Any("Err", err),
		)
	}
}

func SetMongoDbSentryMsgFormat(project string, issue_service string, issue_id string, issue_message string, issue_culprit string, issue_url string, timing string) (msg *SentryMsg) {
	msg = &SentryMsg{
		TraceId:       timing,
		Project:       project,
		Issue_service: issue_service,
		Issue_id:      issue_id,
		Issue_message: issue_message,
		Issue_culprit: issue_culprit,
		Issue_url:     issue_url,
	}
	return msg
}

func SetMongoDbSkyMsgFormat(scope string, name string, ruleName string, alarmMessage string, timing string) (msg *SkyMsg) {
	msg = &SkyMsg{
		TraceId:      timing,
		Scope:        scope,
		Name:         name,
		RuleName:     ruleName,
		AlarmMessage: alarmMessage,
	}
	return msg
}

func ConnMongodb(uri string, timeout time.Duration, num uint64) (*mongo.Client, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	opt := options.Client().ApplyURI(uri)
	opt.SetMaxPoolSize(num)
	opt.SetMinPoolSize(10)
	client, err := mongo.Connect(ctx, opt)
	if err != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("ConnMongDbErr", err),
		)
		return nil, err
	}
	return client, nil

}

func (doc *SentryMsg) PushSentryMsgToMongoDb(c *mongo.Client, alarmType string) {
	collection := c.Database("alarm").Collection(alarmType)
	insertResult, err := collection.InsertOne(context.TODO(), *doc)
	if err != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("TraceId", doc.TraceId),
			zap.Any("ServiceName", doc.Issue_service),
			zap.Any("InsertMongoDbErr", err),
		)
	} else {
		ZapLog.Info("webhook-alarm",
			zap.Any("TraceId", doc.TraceId),
			zap.Any("ServiceName", doc.Issue_service),
			zap.Any("InsertMongoDbSucess", insertResult.InsertedID),
		)
	}

	/*var msg AlarmMsg
	if err = collection.FindOne(context.Background(), bson.M{"_id": insertResult.InsertedID}).Decode(&msg); err != nil {
		//fmt.Println(msg)
	} else {
		fmt.Println(msg)
	}*/
	/*cursors, err := collection.Find(context.Background(), bson.M{"project": "uxshop-server"})
	defer cursors.Close(context.Background())
	var msg common.AlarmMsg
	for cursors.Next(context.Background()) {
		if err = cursors.Decode(&msg); err != nil {
			if err != nil {
				fmt.Println(err)
			}
		}
		fmt.Println(msg)
	}*/

}

func (doc *SkyMsg) PushSkyMsgToMongoDb(c *mongo.Client, alarmType string) {
	collection := c.Database("alarm").Collection(alarmType)
	insertResult, err := collection.InsertOne(context.TODO(), *doc)
	if err != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("TraceId", doc.TraceId),
			zap.Any("RuleName", doc.RuleName),
			zap.Any("InsertMongoDbErr", err),
		)
	} else {
		ZapLog.Info("webhook-alarm",
			zap.Any("TraceId", doc.TraceId),
			zap.Any("RuleName", doc.RuleName),
			zap.Any("InsertMongoDbSucess", insertResult.InsertedID),
		)
	}
}

func PushMsgToMongoDb(c *mongo.Client, doc []byte, alarmType string, timing string) {
	postMongo := []byte("{\"TraceId\":\"")
	postMongo = append(postMongo, []byte(timing)...)
	postMongo = append(postMongo, []byte("\",\"Msg\":")...)
	postMongo = append(postMongo, doc...)
	postMongo = append(postMongo, []byte("}")...)

	res := &struct {
		TraceId string
		Msg     string
	}{
		timing,
		string(doc),
	}

	collection := c.Database("alarm").Collection(alarmType)
	insertResult, err := collection.InsertOne(context.TODO(), *res)
	if err != nil {
		ZapLog.Error("webhook-alarm",
			zap.Any("TraceId", timing),
			zap.Any("Type", "raw faild"),
			zap.Any("RawMsgInsertMongoDbErr", err),
		)
	} else {
		ZapLog.Info("webhook-alarm",
			zap.Any("TraceId", timing),
			zap.Any("Type", "raw message"),
			zap.Any("RawMsgInsertMongoDbSucess", insertResult.InsertedID),
		)
		/*
			var msg struct {
				TraceId string
				Msg     string
			}
			if err = collection.FindOne(context.Background(), bson.M{"_id": insertResult.InsertedID}).Decode(&msg); err != nil {
			} else {
				fmt.Println(msg)
			}
		*/
	}

}
